/* UniApoLib
 * File:   Derivation.h
 * Author: Jeffrey Geyssens
 *
 * This is the header file for the Derivation object.
 * Container for storing non-terminals and the various terminal types in
 * a single container.
 * This is achieved by the implementation of five std maps which each hold
 * the data for a seperate datatype.
 * The key of these maps is an integer, holding all maps together as each
 * key is unique for all maps.
 * This object is used by:
 * -ProductionRuleContainer (For representing the RHS of production rules
 * -StorageRuleContainer (For representing structure of terminal sentences)
 * -ParsedDataString (For reprenting terminal sentences)
 * 
 */

#ifndef _DERIVATION_H
#define	_DERIVATION_H
#include <iostream>
#include <vector>
#include <sstream> //For parameterization
#include <string>
#include "NonTerminal.h"
#include "LiteralTerminal.h"
#include "ReferenceTerminal.h"
#include "DataReferenceTerminal.h"
#include "StoredTerminal.h"
using namespace std;

class Derivation {
public:
    Derivation();
    Derivation(const Derivation& orig);
    virtual ~Derivation();

    friend ostream & operator<<(ostream&, const Derivation&);

    const string& getString() const {
        return ostring;
    }

    string getSpecString();

    void addElement(string x);
    //addElements for SQL generation structure building
    void addElement(StoredTerminal x);
    void addElement(ReferenceTerminal x);
    void addElement(DataReferenceTerminal x);
    void addElement(LiteralTerminal x);
    void addDerivString(string x);
    string getElement(int entryNo);
    string getElementId(int entryNo);

    bool isTerminal();
    void parameterize();
    string getPK(int entryNo);
    void setPK(string s, int entryNo);

    string getValue(int entryNo);
    void setValue(string s, int entryNo);
    void resetValues();

    //Used to update after mapping
    void updateDRT(DataReferenceTerminal& d);
    void updateRT(ReferenceTerminal& r);
    int size();

private:
    map<int, NonTerminal> NTderiv;
    map<int, LiteralTerminal> LTderiv;
    map<int, DataReferenceTerminal> DTderiv;
    map<int, ReferenceTerminal> RTderiv;
    map<int, StoredTerminal> STderiv;
    void rebuildOutputString();

    int next;
    string ostring;




};

Derivation::Derivation() {
    ostring = "";
    next = 1;
}

Derivation::Derivation(const Derivation& orig) {
    ostring = orig.ostring;
    next = orig.next;
    NTderiv = orig.NTderiv;
    STderiv = orig.STderiv;
    RTderiv = orig.RTderiv;
    DTderiv = orig.DTderiv;
    LTderiv = orig.LTderiv;
}

Derivation::~Derivation() {
}

ostream & operator<<(ostream& output, const Derivation& data) {
    //string o;
    //o = data.getString();
    return output << data.getString();
}

void Derivation::addElement(string a) {
    //We check if string is nonterminal


    if ((a.substr(0, 1) == "<") && (a[a.size() - 1] == '>')) {
        //if nonterminal, create the object and add to corresponding map
        NonTerminal n;
        n.setId(a);
        NTderiv[next] = n;
        ostring.append(a);
        next = next + 1;
    }
    //terminals
    if ((a.substr(0, 1) == "(") && (a[a.size() - 1] == ')')) {
        //literal terminal

        if ((a[1] == 'L') && (a[2] == ':')) {

            LiteralTerminal l;
            l.setId(a);

            LTderiv[next] = l;
            ostring.append(a);
            next = next + 1;
        }
        if ((a[1] == 'S') && (a[2] == ':')) {

            StoredTerminal s;
            s.setId(a);

            STderiv[next] = s;
            ostring.append(a);
            next = next + 1;
        }

        if ((a[1] == 'D') && (a[2] == ':')) {
            DataReferenceTerminal d;
            d.setId(a);
            DTderiv[next] = d;
            ostring.append(a);
            next = next + 1;
        }
        if ((a[1] == 'R') && (a[2] == ':')) {
            ReferenceTerminal r;
            r.setId(a);
            RTderiv[next] = r;
            ostring.append(a);
            next = next + 1;
        }

    }


}

void Derivation::addDerivString(string a) {
    string x;
    string temp;
    x = a;
    int i = 0;
    int j = 0;
    while (i < a.size()) //process the string
    {
        j = i + 1;
        //check for NonTerminal

        if (a[i] == '<') {

            while (a[j] != '>') {

                if ((a[j] == '<') || (a[j] == '(') || (a[j] == ')')) {
                    cerr << "Unexpected token" << endl;
                }
                j++;
            }
        }
        //check for Terminals

        if (a[i] == '(') {

            while (a[j] != ')') {

                if ((a[j] == '(') || (a[j] == '<') || (a[j] == '>')) {
                    cerr << "Unexpected token" << endl;
                }
                j++;

            }
        }

        j++;
        temp = x.substr(i, j - i);

        this->addElement(temp);
        i = j;
    }
}

bool Derivation::isTerminal() {
    if (NTderiv.size() == 0) {
        if (0 < (STderiv.size() + LTderiv.size() +
                RTderiv.size() + DTderiv.size()))
            return true;
    }
    return false;
}

//Update Derivation

void Derivation::updateDRT(DataReferenceTerminal& d) {
    const string& searchid = d.getId();
    map<int, DataReferenceTerminal>::iterator ii;

    for (ii = DTderiv.begin();
            ii != DTderiv.end(); ++ii) {
        DataReferenceTerminal& DTelement = (*ii).second;
        cout << DTelement.getId() << endl;

        if (searchid == DTelement.getId()) {

            DTderiv[(*ii).first] = d;
            //hope iterator is ok
        }
    }
}

void Derivation::parameterize() {
    //This method parameterizes terminal derivations
    //Each Terminal is given a unique numerical value.

    //TO-DO

    ostring.erase(ostring.begin(), ostring.end());
    if (this->isTerminal()) //do when terminal derivation
    {
        for (int i = 1; i < next; i++) {
            string pmstr;
            string newid;
            stringstream pm;
            //Create unique numberical value in [].
            pm << "[" << i << "])";
            pmstr = pm.str(); //create string


            map<int, DataReferenceTerminal>::iterator drti;
            drti = DTderiv.find(i);
            if (drti != DTderiv.end()) {
                newid = (*drti).second.getId();
                newid.erase(newid.end() - 1);
                newid.append(pmstr);

                (*drti).second.setId(newid);
            }


            map<int, ReferenceTerminal>::iterator rti;
            rti = RTderiv.find(i);
            if (rti != RTderiv.end()) {
                newid = (*rti).second.getId();
                newid.erase(newid.end() - 1);
                newid.append(pmstr);
                (*rti).second.setId(newid);
            }
            map<int, LiteralTerminal>::iterator lti;
            lti = LTderiv.find(i);
            if (lti != LTderiv.end()) {
                newid = (*lti).second.getId();
                newid.erase(newid.end() - 1);
                newid.append(pmstr);
                (*lti).second.setId(newid);
            }
            this->ostring.append(newid);

        }



    }
}

void Derivation::addElement(ReferenceTerminal a) {
    //We check if string is nonterminal

    RTderiv[next] = a;
    ostring.append(a.getId());
    next = next + 1;

}

void Derivation::addElement(DataReferenceTerminal a) {
    //We check if string is nonterminal

    DTderiv[next] = a;
    ostring.append(a.getId());
    next = next + 1;

}

void Derivation::addElement(StoredTerminal a) {
    //We check if string is nonterminal

    STderiv[next] = a;
    ostring.append(a.getId());
    next = next + 1;

}

void Derivation::addElement(LiteralTerminal a) {
    //We check if string is nonterminal

    LTderiv[next] = a;
    ostring.append(a.getId());
    next = next + 1;

}

string Derivation::getElement(int entryNo) {
    if ((entryNo << next) && (entryNo >> 0)) {
        map<int, NonTerminal>::iterator nti;
        nti = NTderiv.find(entryNo);
        if (nti != NTderiv.end())
            return (*nti).second.getId();
        map<int, DataReferenceTerminal>::iterator dti;
        dti = DTderiv.find(entryNo);
        if (dti != DTderiv.end())
            return (*dti).second.getId();
        map<int, ReferenceTerminal>::iterator rti;
        rti = RTderiv.find(entryNo);
        if (rti != RTderiv.end())
            return (*rti).second.getId();
        map<int, LiteralTerminal>::iterator lti;
        lti = LTderiv.find(entryNo);
        if (lti != LTderiv.end())
            return (*lti).second.getId();
        map<int, StoredTerminal>::iterator sti;
        sti = STderiv.find(entryNo);
        if (sti != STderiv.end())
            return (*sti).second.getId();

    } else {
        cerr << "Derivation Element entry number out of bounds" << endl;
    }
}

string Derivation::getElementId(int entryNo) {
    if ((entryNo << next) && (entryNo >> 0)) {
        map<int, NonTerminal>::iterator nti;
        nti = NTderiv.find(entryNo);
        if (nti != NTderiv.end())
            return (*nti).second.getSID();
        map<int, DataReferenceTerminal>::iterator dti;
        dti = DTderiv.find(entryNo);
        if (dti != DTderiv.end())
            return (*dti).second.getDSID();
        map<int, ReferenceTerminal>::iterator rti;
        rti = RTderiv.find(entryNo);
        if (rti != RTderiv.end())
            return (*rti).second.getSID();
        map<int, LiteralTerminal>::iterator lti;
        lti = LTderiv.find(entryNo);
        if (lti != LTderiv.end())
            return (*lti).second.getSID();
        map<int, StoredTerminal>::iterator sti;
        sti = STderiv.find(entryNo);
        if (sti != STderiv.end())
            return (*sti).second.getSID();

    } else {
        cerr << "Derivation Element entry number out of bounds" << endl;
    }
}

void Derivation::resetValues() {
    map<int, DataReferenceTerminal>::iterator dti;
    for (dti = DTderiv.begin(); dti != DTderiv.end(); ++dti)
        (*dti).second.setValue("");
    map<int, ReferenceTerminal>::iterator rti;
    for (rti = RTderiv.begin(); rti != RTderiv.end(); ++rti)
        (*rti).second.setValue("");
    map<int, LiteralTerminal>::iterator lti;
    for (lti = LTderiv.begin(); lti != LTderiv.end(); ++lti)
        (*lti).second.setValue("");
    map<int, StoredTerminal>::iterator sti;
    for (sti = STderiv.begin(); sti != STderiv.end(); ++sti)
        (*sti).second.setValue("");
}

string Derivation::getPK(int entryNo) {
    if ((entryNo << next) && (entryNo >> 0)) {

        map<int, DataReferenceTerminal>::iterator dti;
        dti = DTderiv.find(entryNo);
        if (dti != DTderiv.end())
            return (*dti).second.getValue();

        map<int, ReferenceTerminal>::iterator rti;
        rti = RTderiv.find(entryNo);
        if (rti != RTderiv.end())
            return (*rti).second.getValue();

        map<int, LiteralTerminal>::iterator lti;
        lti = LTderiv.find(entryNo);
        if (lti != LTderiv.end())
            return (*lti).second.getValue();

        map<int, StoredTerminal>::iterator sti;
        sti = STderiv.find(entryNo);
        if (sti != STderiv.end())
            return (*sti).second.getValue();

    } else {
        cerr << "Derivation Element entry number out of bounds" << endl;
    }
}

string Derivation::getValue(int entryNo) {
    if ((entryNo << next) && (entryNo >> 0)) {

        map<int, DataReferenceTerminal>::iterator dti;
        dti = DTderiv.find(entryNo);
        if (dti != DTderiv.end())
            return (*dti).second.getValue();

        map<int, ReferenceTerminal>::iterator rti;
        rti = RTderiv.find(entryNo);
        if (rti != RTderiv.end())
            return (*rti).second.getValue();

        map<int, LiteralTerminal>::iterator lti;
        lti = LTderiv.find(entryNo);
        if (lti != LTderiv.end())
            return (*lti).second.getValue();

        map<int, StoredTerminal>::iterator sti;
        sti = STderiv.find(entryNo);
        if (sti != STderiv.end())
            return (*sti).second.getValue();

    } else {
        cerr << "Derivation Element entry number out of bounds" << endl;
    }
}

void Derivation::setValue(string s, int entryNo) {
    if ((entryNo << next) && (entryNo >> 0)) {

        map<int, DataReferenceTerminal>::iterator dti;
        dti = DTderiv.find(entryNo);
        if (dti != DTderiv.end())
            return (*dti).second.setValue(s);
        map<int, ReferenceTerminal>::iterator rti;
        rti = RTderiv.find(entryNo);
        if (rti != RTderiv.end())
            return (*rti).second.setValue(s);
        map<int, LiteralTerminal>::iterator lti;
        lti = LTderiv.find(entryNo);
        if (lti != LTderiv.end())
            return (*lti).second.setValue(s);
        map<int, StoredTerminal>::iterator sti;
        sti = STderiv.find(entryNo);
        if (sti != STderiv.end())
            return (*sti).second.setValue(s);

    } else {
        cerr << "Derivation Element entry number out of bounds" << endl;
    }

}

string Derivation::getSpecString() {
    string x;
    int i;
    i = 1;
    for (int i = 1; i < next; i++) {
        x.append(this->getElementId(i));
    }
    return x;
}

int Derivation::size() {
    return next-1;
}



#endif	/* _DERIVATION_H */

